package cn.edu.neu.lab603.cloudeval.strategy.policy

import cn.edu.neu.lab603.cloudeval.entity.{DataCenter, PhysicalMachine => Host, VirtualMachine => VM}

import scala.collection.mutable

/** 基于使用率的主机选择策略。
  *
  * Created by yfwz100 on 2016/11/9.
  */
abstract class HostSelectionPolicyUtilizationBase(safety: Double = 1) extends HostSelectionPolicy {

  import MigrationDef._

  protected val allocatedMap = new mutable.HashMap[Host, mutable.HashMap[String, Int]]
  // 高载主机列表
  protected var overloadHosts: Set[Host] = _
  // 低载主机列表
  protected var underloadHosts: Seq[Host] = _

  override def getHostsToMigrate(implicit dataCenter: DataCenter): Iterable[Host] = {
    allocatedMap.clear()

    val availableHosts = dataCenter.getRunningHost
      .filterNot(_.getVMList.exists(_.getStatus == VM.Status.MIGRATING)) // 避免频繁迁移
    val (orderedOverloadHosts, remainHosts) = availableHosts.span(isOverloadHost)
    overloadHosts = orderedOverloadHosts.toSet
    underloadHosts = remainHosts.filter(isUnderloadHost).sortBy(_.getUsedResPct("mips"))

    orderedOverloadHosts ++ underloadHosts
  }

  /** 检测是否为低载物理服务器。
    *
    * @param host 物理服务器
    * @return 布尔值
    */
  def isUnderloadHost(host: Host): Boolean = !(overloadHosts contains host)

  /** 检测是否为过载物理服务器。
    *
    * @param host 物理服务器
    * @return 布尔值
    */
  def isOverloadHost(host: Host): Boolean

  override def getHostForPlacement(vm: VM)(implicit dataCenter: DataCenter): Host = {
    if (overloadHosts contains vm.host) {
      getPlacementForVMinOverloadHost(vm)
    } else {
      getPlacementForVMinUnderloadHost(vm)
    }
  }

  protected def getPlacementForVMinOverloadHost(vm: VM)(implicit dataCenter: DataCenter): Host = {
    val runningHosts = underloadHosts
    val hostOption = runningHosts.reverse.find { h =>
      h.getRemainSpecRes.forall { p =>
        val allocated = allocatedMap.getOrElseUpdate(h, mutable.HashMap.empty).getOrElse(p._1, 0)
        val r: Double = p._2 - allocated - vm.getUsedResMap(p._1)

        if (r > 0) r / h.capacity(p._1) <= safety else false
      }
    }
    if (hostOption.isDefined) {
      val host = hostOption.get
      val resMap = allocatedMap.getOrElseUpdate(host, mutable.HashMap.empty)
      vm.getSpecResMap.foreach { p =>
        resMap(p._1) = resMap.getOrElse(p._1, 0) + p._2
      }
      host
    } else null
  }

  protected def getPlacementForVMinUnderloadHost(vm: VM)(implicit dataCenter: DataCenter): Host = {
    val hostOption = underloadHosts
      .filter(_ != vm.host)
      .sortBy(h => -h.getVMList.size - h.getUsedResPct("mips"))
      .find { h =>
        (h.capacity.forall(p => vm.host.capacity(p._1) < p._2) || (h.capacity.forall(p => vm.host.capacity(p._1) <= p._2) && h.getVMList.size > vm.host.getVMList.size)) && h.getRemainSpecRes.forall { p =>
          val allocated = allocatedMap.getOrElseUpdate(h, mutable.HashMap.empty).getOrElse(p._1, 0)
          val r: Double = p._2 - allocated - vm.getUsedResMap(p._1)
          if (r > 0) r / h.capacity(p._1) <= safety else false
        }
      }
    if (hostOption.isDefined && hostOption.get != vm.host) {
      val host = hostOption.get
      val resMap = allocatedMap.getOrElseUpdate(host, mutable.HashMap.empty)
      vm.getSpecResMap.foreach { p =>
        resMap(p._1) = resMap.getOrElse(p._1, 0) + p._2
      }
      host
    } else {
      //      val hostOption = dataCenter.pmList
      //        .filter(_.getStatus == Host.Status.OFF)
      //        .find(_.capacity.forall(p => p._2 >= vm.host.capacity(p._1)))
      //      hostOption.map { h => h.boot(); h }.orNull
      //      hostOption.foreach(_.boot())
      null
    }
  }

  override def getHostsToTurnoff(implicit dataCenter: DataCenter): Iterable[Host] = {
    val idle = underloadHosts.view.filterNot(allocatedMap.contains).filter(_.isIdle)
    idle.drop(idle.size * 2 / 3)
  }

  /** 转换到迁移时间判断策略。
    *
    * @return 迁移时机判断策略
    */
  def getReallocationTimePolicy(checkPeriod: Int = 25) = ReAllocationTimePolicyUtilizationBase(this, checkPeriod)

}
